/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package genomemap.maps;

import javautil.collections.ArrayUtil;
import javautil.collections.SetUtil;
import javautil.swing.SwingUtil;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.swing.JPanel;

/**
 * Class description
 *
 * @version        Enter version here..., 12/11/23
 * @author         Susanta Tewari
 */
public class XMap extends JPanel {

    /** Field description */
    private final int MAP_HEADER_VERT_INDENT = 40;

    /** Field description */
    private int MAP_HEADER_HORI_INDENT = 40;

    /** Field description */
    private final int INTER_LABEL_VERT_GAP = 5;

    /** Field description */
    private final int LABEL_LINE_GAP = 5;

    /** Field description */
    private final int INTER_MAP_GAP = 150;

    /** Field description */
    private final Font textFont = new Font("Courier New", Font.BOLD, 12);

    /** Field description */
    private final Color lineColor = Color.RED;

    /** Field description */
    private transient BasicStroke lineStroke = null;

    /** Field description */
    private int mapWIDTH = 0;

    /** Field description */
    private int mapHEIGHT = 0;

    /** Field description */
    private int[][] MAP_STR_LEN = null;

    /** Field description */
    private int[] MAP_X = null;

    /** Field description */
    private int mapTotal = 0;

    /** Field description */
    private String[] mapHeaders = null;

    /** Field description */
    private boolean[] anyCross = null;

    /** Field description */
    private int[] crossCounts = null;

    /** Field description */
    private LinkedHashSet[] labelSetsUsed = null;

    /** Field description */
    private int LABEL_HEIGHT;

    /**
     * Constructs ...
     */
    public XMap() {
        lineStroke = new BasicStroke(2.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
    }

    public void instantiate(LinkedHashSet[] labelSetsGiven, String[] mapHeaders, boolean[] anyCross)
            throws Exception {

        this.LABEL_HEIGHT = this.getFontMetrics(textFont).getHeight();
        this.MAP_HEADER_HORI_INDENT = ArrayUtil.findMax(getPixelWidth(mapHeaders, this.textFont))
                                      / 2;
        this.mapHeaders   = mapHeaders;

        /**
         * anyCross has length = mapTotal - 1. boolean values indicate if any lines should be drawn between
         * the maps. provide null for a single map.
         */
        this.anyCross    = anyCross;
        this.crossCounts = new int[anyCross.length];

        if (labelSetsGiven.length != mapHeaders.length) {

            System.out.println("labelSets length: " + labelSetsGiven.length
                               + " and mapHeaders Length: " + mapHeaders.length);

            throw new Exception("argument lengths do not match");
        }

        this.mapTotal = labelSetsGiven.length;

        if ((anyCross != null) && (mapTotal != anyCross.length + 1))
            throw new Exception("argument length does not match for boolean array anyCross");
        if ((mapTotal == 1) && (anyCross != null))
            throw new Exception("anyCross must be null for single map construction.");

        this.MAP_STR_LEN   = new int[mapTotal][];
        this.labelSetsUsed = new LinkedHashSet[mapTotal];
        this.MAP_X         = new int[mapTotal];

        /**
         * get the subsequence of gene labels
         */


        // Get the common set from all then apply subSeq  to get them in their order.
        Set commonSet = labelSetsGiven[0];

        for (int i = 1; i < mapTotal; i++) {

            commonSet = SetUtil.getCommonSet(commonSet, labelSetsGiven[i]);
        }

        if (commonSet.size() == 0) {
            throw new Exception("Common set size is zero");
        }

        for (int i = 0; i < mapTotal; i++) {

            this.labelSetsUsed[i] = SetUtil.getSubSeq(labelSetsGiven[i], commonSet);
            this.MAP_STR_LEN[i]   = getPixelWidth(this.labelSetsUsed[i], this.textFont);


            // set x coordinates of maps
            if (i == 0) {
                this.MAP_X[0] = this.MAP_HEADER_HORI_INDENT;
            } else {

                this.MAP_X[i] = this.MAP_X[i - 1] + ArrayUtil.findMax(this.MAP_STR_LEN[i - 1])
                                + this.INTER_MAP_GAP;
            }

            this.mapWIDTH += ArrayUtil.findMax(this.MAP_STR_LEN[i]);
        }

        this.mapHEIGHT = 2 * MAP_HEADER_VERT_INDENT
                         + (labelSetsUsed[0].size() - 1) * this.INTER_LABEL_VERT_GAP
                         + labelSetsUsed[0].size() * this.LABEL_HEIGHT;
        this.mapWIDTH += 2 * MAP_HEADER_HORI_INDENT + (mapTotal - 1) * this.INTER_MAP_GAP;

        setPreferredSize(new Dimension(mapWIDTH, mapHEIGHT));
        setBackground(Color.white);
    }

    @Override
    public int getHeight() {
        return mapHEIGHT;
    }

    @Override
    public int getWidth() {
        return mapWIDTH;
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);

        if (lineStroke == null)
            lineStroke = new BasicStroke(2.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);

        Graphics2D g2 = (Graphics2D) g;

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setStroke(lineStroke);


        // create background
        g2.setColor(Color.white);
        g2.fillRect(0, 0, mapWIDTH, mapHEIGHT);


        // set up text color and font and rendering hints
        g2.setColor(Color.black);
        g2.setFont(textFont);

        for (int i = 0; i < mapTotal; i++) {

            String[] labels = (String[]) labelSetsUsed[i].toArray(new String[0]);


            // Draw map header
            g2.drawString(mapHeaders[i], MAP_X[i] + findWordPixelWidth(labels[0], textFont) / 2
                          - findWordPixelWidth(mapHeaders[i], textFont) / 2, MAP_HEADER_VERT_INDENT
                            / 2);


            // Draw map
            for (int labelIndex = 0; labelIndex < labels.length; labelIndex++) {

                g2.drawString(labels[labelIndex], MAP_X[i],
                              MAP_HEADER_VERT_INDENT
                              + labelIndex * (LABEL_HEIGHT + INTER_LABEL_VERT_GAP));
            }
        }


        // Draw lines if applicable : from (i-1) th map -> ith map
        if (anyCross != null) {

            g2.setStroke(lineStroke);
            g2.setColor(lineColor);

            int crossMapIndex = 0;

            for (int i = 1; i < mapTotal; i++) {

                if (anyCross[crossMapIndex]) {

                    String[] labels_i       = (String[]) labelSetsUsed[i].toArray(new String[0]);
                    String[] labels_iMinus1 =
                        (String[]) labelSetsUsed[i - 1].toArray(new String[0]);

                    for (int labelIndex = 0; labelIndex < labels_iMinus1.length; labelIndex++) {

                        int index = ArrayUtil.searchStringIgnoreCase(labels_iMinus1[labelIndex],
                                        labels_i);

                        if (index != labelIndex) {


                            // There is a cross for this label
                            crossCounts[crossMapIndex]++;
                        }

                        if (index == -1)
                            SwingUtil.showMessageDialog(null,
                                                        "line: map" + (i - 1) + " -> map" + i
                                                        + ". label: " + labels_iMinus1[labelIndex]
                                                        + " not found");
                        else
                            g2.drawLine(
                                MAP_X[i - 1] + MAP_STR_LEN[i - 1][labelIndex]
                                + LABEL_LINE_GAP, MAP_HEADER_VERT_INDENT
                                                  + labelIndex
                                                    * (LABEL_HEIGHT
                                                       + INTER_LABEL_VERT_GAP) - 2, MAP_X[i]
                                                           - LABEL_LINE_GAP, MAP_HEADER_VERT_INDENT
                                                               + index
                                                                   * (LABEL_HEIGHT
                                                                       + INTER_LABEL_VERT_GAP) - 2);
                    }

                    crossMapIndex++;
                }
            }


            // temporary comptation : line crossings between the first and the 3rd
            int tempCrossCount      = 0;
            String[] labels_i       = (String[]) labelSetsUsed[2].toArray(new String[0]);
            String[] labels_iMinus1 = (String[]) labelSetsUsed[0].toArray(new String[0]);

            for (int labelIndex = 0; labelIndex < labels_iMinus1.length; labelIndex++) {

                int index = ArrayUtil.searchStringIgnoreCase(labels_iMinus1[labelIndex], labels_i);

                if (index != labelIndex) {


                    // There is a cross for this label
                    tempCrossCount++;
                }
            }

            System.out.println("Crossing count between 1st and 3rd: " + tempCrossCount);
        }
    }

    /**
     * Method description
     *
     * @param strSet description
     * @param font description
     *
     * @return description
     */
    private int[] getPixelWidth(Set strSet, Font font) {

        int[] temp = new int[strSet.size()];
        int i      = 0;

        for (Iterator itr = strSet.iterator(); itr.hasNext(); ) {

            String str = (String) itr.next();

            temp[i] = this.getFontMetrics(font).stringWidth(str.trim());

            i++;
        }

        return temp;
    }

    /**
     * Method description
     *
     * @param words description
     * @param font description
     *
     * @return description
     */
    private int[] getPixelWidth(String[] words, Font font) {

        int[] temp = new int[words.length];

        for (int i = 0; i < words.length; i++) {

            temp[i] = findWordPixelWidth(words[i], font);
        }

        return temp;
    }

    /**
     * Method description
     *
     * @param word description
     * @param font description
     *
     * @return description
     */
    private int findWordPixelWidth(String word, Font font) {
        return (this.getFontMetrics(font).stringWidth(word));
    }

    public int[] getCrossCounts() {
        return crossCounts;
    }
}
